package de.sendorian.db;


import java.util.List;
import java.util.logging.Logger;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;

/**
 * Super class for DAOs which declares the essential functions.
 *
 * @author sendorian
 *
 */
public abstract class DAO<T> {
	protected Logger logger = Logger.getLogger(DAO.class.getName());

	private static final ThreadLocal<EntityManager> em = new ThreadLocal<EntityManager>();

	private static EntityManagerFactory factory;

	protected final Class<T> entity;

	protected String entityName;


	protected DAO(Class<T> entity) {
		this.entity = entity;
		entityName = entity.getSimpleName();
	}


	public static boolean initialized() {
		return factory != null;
	}


	public static void init(String persistenceUnit) {
		if (persistenceUnit == null) {
			throw new IllegalArgumentException("persistenceUnit cannot be null");
		}

		if (factory != null) {
			throw new IllegalStateException("DAO is already initialized!");
		}

		factory = Persistence.createEntityManagerFactory(persistenceUnit);
	}


	public static EntityManager getEntityManager() {
		if (factory == null) {
			throw new IllegalStateException("EntityManagerFactory is not initialized!");
		}

		EntityManager em = DAO.em.get();
		if (em == null || !em.isOpen()) {
			em = factory.createEntityManager();
			DAO.em.set(em);
		}
		return em;
	}


	protected static void begin() {
		getEntityManager().getTransaction().begin();
	}


	protected static void commit() {
		getEntityManager().getTransaction().commit();
	}


	protected static void rollback() {
		try {
			getEntityManager().getTransaction().rollback();
		} finally {
			close();
		}
	}


	public static void close() {
		getEntityManager().close();
		DAO.em.set(null);
	}


	public static final <E> E update(E entity) {
		begin();
		E result = DAO.getEntityManager().merge(entity);
		commit();
		return result;
	}


	public static final <E> E save(E entity) {
		begin();
		DAO.getEntityManager().persist(entity);
		commit();
 		return entity;
	}


	public static final void saveOrUpdate(Object entity) {
		Integer id = null;
		try {
			id = (Integer) entity.getClass().getMethod("getId", null).invoke(entity, null);
		} catch (Exception e) {
			throw new IllegalArgumentException("Entity has no ID attribute! " + entity, e);
		}
		if (id != null && id.intValue() > 0) {
			update(entity);
		} else {
			save(entity);
        }
	}


	public static final void delete(Object entity) {
		begin();
		DAO.getEntityManager().remove(entity);
		commit();
	}


	public final int deleteById(Object id) {
		return deleteByAttribute("id", id);
	}


	public final int deleteByAttribute(String attribute, Object value) {
		begin();
		int count = getEntityManager().createQuery(
		    "delete from " + entityName + " where " + attribute + " = :value").setParameter(
		    "value", value).executeUpdate();
		commit();
		return count;
	}


	/**
	 * Loads the entity with the given ID from the database. The Type of the entity is set through
	 * the type parameter (and can be of any entity type!).
	 *
	 * @return The entity, null if not found.
	 */
	public static final <E> E findById(Class<E> type, Object key) {
		return DAO.getEntityManager().find(type, key);
	}


	/**
	 * Loads the entity with the given ID from the database. The Type of the entity is set through
	 * the type of this DAO.
	 *
	 * @return The entity, null if not found.
	 */
	public final T findById(Object id) {
		return DAO.getEntityManager().find(entity, id);
	}


	public final boolean existsById(Object key) {
		return findById(key) != null;
	}


	@SuppressWarnings("unchecked")
	public final List<T> findByAttribute(String attribute, Object value) {
		return getEntityManager().createQuery(
		    "select t from " + entityName + " t where t." + attribute + " = :value").setParameter(
		    "value", value).getResultList();
	}


	@SuppressWarnings("unchecked")
	public final T findByUniqueAttribute(String attribute, Object value) {
		return (T) getEntityManager().createQuery(
		    "select t from " + entityName + " t where t." + attribute + " = :value").setParameter(
		    "value", value).getSingleResult();
	}


	// public static final boolean existsEntity (Object entity) {
	// if (entity == null) {
	// return false;
	// }
	// try {
	// Object id = Utils.getFieldWithReflection (entity, "id");
	// if (id == null) {
	// return false;
	// }
	//
	// return load (entity.getClass (), id) != null;
	// } catch (Exception e) {
	// logger.warn ("Entity " + entity.getClass ().getSimpleName ()
	// + " has no ID field!");
	// return false;
	// }
	// }

	/**
	 * Creates a simple query based on the entity type of this DAO and one parameter that has to
	 * have a given value.
	 *
	 * @param paramName
	 *            name of the property
	 * @param paramValue
	 *            the value
	 * @return a query representing the given parameters
	 */
	protected Query createQuery(String propertyName, Object propertyValue) {
		String jpql = "select e from " + entityName + " e where e." + propertyName + " = :"
		    + propertyName;
		Query query = getEntityManager().createQuery(jpql);
		query.setParameter(propertyName, propertyValue);
		return query;
	}


	public void deleteAll() {
		begin();
		getEntityManager().createQuery("delete from " + entityName).executeUpdate();
		commit();
	}


	@SuppressWarnings("unchecked")
	public List<T> findAll() {
		String jpql = "select e from " + entityName + " e";
		return getEntityManager().createQuery(jpql).getResultList();
	}


	/**
	 * Searches for entities for which the given String property matches the given
	 * propertyValuePattern (ignore case).
	 *
	 */
	@SuppressWarnings("unchecked")
	public List<T> search(String propertyName, String propertyValuePattern) {
		if (propertyName == null || propertyValuePattern == null) {
			throw new IllegalArgumentException("null values not allowed.");
		}

		String jpql = "select e from " + entityName + " e where upper(e." + propertyName
		    + ") like :" + propertyName;
		Query query = getEntityManager().createQuery(jpql);
		query.setParameter(propertyName, propertyValuePattern.toUpperCase());
		return query.getResultList();
	}

	public static void shutdownDatabase() {
		factory.close();
	}

	/**
	 * Returns the IDs of the given entities.
	 *
	 * Returns an empty list, if any error occurs or the input is null or empty.
	 */
	// @SuppressWarnings("unchecked")
	// protected List<Object> getIDs(Collection entities) {
	// if (entities == null || entities.isEmpty ()) {
	// return Collections.EMPTY_LIST;
	// }
	//
	// List<Object> ids = new ArrayList<Object>(entities.size ());
	//
	// for (Object entity : entities) {
	// try {
	// Object id = Utils.getFieldWithReflection (entity, "id");
	// if (id != null) {
	// ids.add (id);
	// }
	// } catch (Exception e) {
	// logger.warn ("Entity " + entity.getClass ().getSimpleName ()
	// + " has no ID field!");
	// }
	// }
	// return ids;
	// }
}
